/*
 * serial_io.c
 *
 * serial port routines
 *
 * Copyright (C) 2020 Long-Horn
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#include "serial_io.h"

static int  serial_io_isopen(void *sio)
{
    serial_io_s *psio = (serial_io_s *)sio;
    return (psio->fd > 0);
}

static int serial_io_open(void *sio)
{
    struct termios t;
    speed_t speed;
    int fd, c_stop, c_data, i_data, i_parity, c_parity;
    
    if (serial_io_isopen(sio))
        return -1;

    serial_io_s *psio = (serial_io_s *)sio;
    fd = open(psio->info.portname, O_RDWR);
    if (fd == -1)
        return -1;
    
    if (tcgetattr(fd, &t)) {
        close(fd);
        return -1;
    }
    
    switch(psio->info.baud) {
        case 0: speed = B0; break;
        case 50: speed = B50; break;
        case 75: speed = B75; break;
        case 110: speed = B110; break;
        case 134: speed = B134; break;
        case 150: speed = B150; break;
        case 300: speed = B300; break;
        case 600: speed = B600; break;
        case 1200: speed = B1200; break;
        case 1800: speed = B1800; break;
        case 2400: speed = B2400; break;
        case 4800: speed = B4800; break;
        case 9600: speed = B9600; break;
        case 19200: speed = B19200; break;
        case 38400: speed = B38400; break;
        case 57600: speed = B57600; break;
        case 115200: speed = B115200; break;
        case 230400: speed = B230400; break;
        default: speed = B0; break;
    }

    if (speed == B0) {
        close(fd);
        return -1;
    }
	
    if (cfsetospeed(&t, speed)) {
        close(fd);
        return -1;
    }
    
    if (cfsetispeed(&t, speed)) {
        close(fd);
        return -1;
    }
    
    switch(psio->info.stopbits) {
        case 1: c_stop = 0; break;
        case 2: c_stop = CSTOPB; break;
        default: close(fd); return -1;
    }
    
    switch(psio->info.databits) {
        case 5: c_data = CS5; break;
        case 6: c_data = CS6; break;
        case 7: c_data = CS7; break;
        case 8: c_data = CS8; break;
        default: close(fd); return -1;
    }
    
    switch(psio->info.parity) {
        case SIO_PARITY_NONE:
            i_parity = IGNPAR;
            c_parity = 0;
            break;
        case SIO_PARITY_EVEN:
            i_parity = INPCK;
            c_parity = PARENB;
            break;
        case SIO_PARITY_ODD:
            i_parity = INPCK;
            c_parity = PARENB | PARODD;
            break;
        default:
            close(fd);
            return -1;
    }
    
    if (tcsetattr(fd, TCSANOW, &t)) {
        close(fd);
        return -1;
    }
    
    psio->fd = fd;
    
    return 0;
}

static void serial_io_close(void *sio)
{
    serial_io_s *psio = (serial_io_s *)sio;
    if (serial_io_isopen(sio)) {
        close(psio->fd);
        psio->fd = 0;
    }
}

static int serial_io_write(void *sio, void *buffer, size_t count)
{
    int written_len;
    
    serial_io_s *psio = (serial_io_s *)sio;
    pthread_mutex_lock(&psio->write_mutex);
    written_len = write(psio->fd, buffer, count);
    pthread_mutex_unlock(&psio->write_mutex);
    
    return written_len;
}

static int serial_io_read(void *sio, void *buffer, size_t count)
{
    int read_len;
    
    serial_io_s *psio = (serial_io_s *)sio;
    pthread_mutex_lock(&psio->read_mutex);
    read_len = read(psio->fd, buffer, count);
    pthread_mutex_unlock(&psio->read_mutex);
    
    return read_len;
}

static int serial_io_write_timeout(void *sio, void *buffer, size_t count, size_t ms)
{
    serial_io_s *psio = (serial_io_s *)sio;
    return 0;
}

static int serial_io_read_timeout(void *sio, void *buffer, size_t count, size_t ms)
{
    serial_io_s *psio = (serial_io_s *)sio;
    return 0;
}

static int serial_io_write_with_response(void *sio, void *buffer, size_t count, void *response)
{
    serial_io_s *psio = (serial_io_s *)sio;
    return 0;
}

static void serial_io_flush(void *sio, int dir)
{
    serial_io_s *psio = (serial_io_s *)sio;
    if (serial_io_isopen(psio)) {
        switch(dir) {
            case SIO_IN: tcflush(psio->fd, TCIFLUSH); break;
            case SIO_OUT: tcflush(psio->fd, TCOFLUSH); break;
            case SIO_IN | SIO_OUT: tcflush(psio->fd, TCIOFLUSH); break;
        }
    }
}

static void serial_io_drain(void *sio)
{
    serial_io_s *psio = (serial_io_s *)sio;
    if (serial_io_isopen(psio)) {
        tcdrain(psio->fd);
    }
}

static void serial_io_setinfo(void *sio, serialinfo_s *info)
{
    serial_io_s *psio = (serial_io_s *)sio;
    psio->info = *info;
    return;
}

int create_serial_io(serial_io_s **sio, const char *portname, int baud_rate)
{
    serial_io_s *p_sio = NULL;
    p_sio = (void *)malloc(sizeof(void));
    if(NULL == p_sio) {
        printf("### %s: malloc void failed!\n", __func__);
       return -1;
    }
    
    pthread_mutex_init(&p_sio->read_mutex, NULL);
    pthread_mutex_init(&p_sio->write_mutex, NULL);
    
    serialinfo_s info;
    info.baud = baud_rate;
    strcpy(info.portname, portname);
    info.stopbits = 1;
    info.databits = 8;
    info.parity = SIO_PARITY_NONE;
    serial_io_setinfo((void *)p_sio, &info);
    
    p_sio->open = serial_io_open;
    p_sio->close = serial_io_close;
    p_sio->read = serial_io_read;
    p_sio->write = serial_io_write;
    p_sio->read_timeout = serial_io_read_timeout;
    p_sio->write_timeout = serial_io_write_timeout;
    p_sio->write_with_response = serial_io_write_with_response;
    
    *sio = p_sio;
    
    printf("%s: OK!\n", __func__);
    	
    return 0;
}

void destroy_serial_io(void *sio)
{
    serial_io_s *psio = (serial_io_s *)sio;
    if (NULL != sio) {
        pthread_mutex_destroy(&psio->read_mutex);
        pthread_mutex_destroy(&psio->read_mutex);
        
        free(sio);
    }
}

